﻿// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)

using System.Collections.Generic;
using Bravura.Core.PluginLib;

namespace Bravura.Native.PluginBase
{
	/// <summary>
	/// Base class for voiced instruments.
	/// </summary>
	public abstract class Instrument<T> : AudioPlugin where T : Voice
	{
		#region Fields

		/// <summary>
		/// Buffer length.
		/// </summary>
		protected int _buffLen = 100;

		/// <summary>
		/// Sample buffer 0 (left).
		/// </summary>
		protected double[] _buff0 = null;

		/// <summary>
		/// Sample buffer 1 (right).
		/// </summary>
		protected double[] _buff1 = null;

		/// <summary>
		/// Maximum voice count.
		/// </summary>
		protected int _vc = 1;

		/// <summary>
		/// Array of all voices, whether instanced or not.
		/// </summary>
		protected T[] _voiceArray = null;

		/// <summary>
		/// List of instanced voices.
		/// </summary>
		protected List<T> _voiceList = null;

		#endregion

		#region Configuration Properties

		/// <summary>
		/// Attack time for a voiced note.
		/// </summary>
		public double AttackTime
		{
			get;
			set;
		}

		/// <summary>
		/// Release time for a voiced note.
		/// </summary>
		public double ReleaseTime
		{
			get;
			set;
		}

		/// <summary>
		/// Non-linear distortion effect (applied to a voice).
		/// </summary>
		public double Distortion
		{
			get;
			set;
		}

		#endregion

		protected Instrument(IAudioPluginHost host, int sampleRate, int voiceCount) : base(host, sampleRate)
		{
			// set default field values
			_buff0 = new double[_buffLen];
			_buff1 = new double[_buffLen];
			_vc = voiceCount;
			_voiceList = new List<T>();
			_voiceArray = new T[_vc];

			// set default property values
			this.AttackTime = 0.125;
			this.ReleaseTime = 0.500;
			this.Distortion = 1.000;
		}

		public abstract T CreateVoice();

		public virtual void UpdateVoice(T voice)
		{
			voice.Update(1.0 / this.AttackTime, 1.0 / this.ReleaseTime, this.Distortion);
		}

		#region IInstrument

		public void SendVoiceNoteOn(int voiceNum, double? sound, double? pitch, double? velocity)
		{
			if (voiceNum >= 0 && voiceNum < _vc)
			{
				// ensure the voice is instanced
				if (_voiceArray[voiceNum] == null)
				{
					// create a new voice
					T temp = CreateVoice();

					// place into voice array
					_voiceArray[voiceNum] = temp;

					// add to instanced voice list
					_voiceList.Add(temp);
				}
				else
				{
					// update the voice
					UpdateVoice(_voiceArray[voiceNum]);
				}

				// send the note on command
				_voiceArray[voiceNum].NoteOn(
					sound ?? 0.0,
					pitch ?? 60.0,
					velocity ?? 1.0
				);
			}
		}

		public void SendVoiceNoteOff(int voiceNum)
		{
			if (voiceNum >= 0 && voiceNum < _vc)
			{
				// ensure the voice is instanced
				if (_voiceArray[voiceNum] != null)
				{
					// send the note off command
					_voiceArray[voiceNum].NoteOff();
				}
			}
		}

		public void SetVoiceSetting(int voiceNum, string key, string value)
		{
			if (voiceNum >= 0 && voiceNum < _vc)
			{
				// ensure the voice is instanced
				if (_voiceArray[voiceNum] != null)
				{
					// set the note setting
					_voiceArray[voiceNum].NoteSetting(key, value);
				}
			}
		}

		public void SetVoiceControl(int voiceNum, string key, double value)
		{
			if (voiceNum >= 0 && voiceNum < _vc)
			{
				// ensure the voice is instanced
				if (_voiceArray[voiceNum] != null)
				{
					// set the note control
					_voiceArray[voiceNum].NoteControl(key, value);
				}
			}
		}

		public override void Open()
		{
		}

		public override void Process(int count)
		{
			// make sure the buffers are big enough
			if (count > _buffLen)
			{
				_buff0 = new double[count];
				_buff1 = new double[count];
				_buffLen = count;
			}

			double[] sample = null;

			// for each sample
			for (int i = 0; i < count; i++)
			{
				// start at zero
				double s0 = 0.0;
				double s1 = 0.0;

				// add sample from each instanced voice
				for (int v = 0, vc = _voiceList.Count; v < vc; v++)
				{
					sample = _voiceList[v].Sample();
					s0 += sample[0];
					s1 += sample[1];
				}

				// put sample into buffers
				_buff0[i] = s0;
				_buff1[i] = s1;
			}

			// write to output buffers
			GetOutputBuffer(0).Write(_buff0, count);
			GetOutputBuffer(1).Write(_buff1, count);
		}

		public override void Close()
		{
		}

		#endregion
	}
}
